package cn.yjh.context;

import cn.yjh.Event;
import cn.yjh.Listener;
import cn.yjh.Multicaster;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import java.lang.reflect.Type;
import java.util.*;

/**
 * @DESC:
 * @AUTHOR: YouJinhua
 * @DATE: 2021-11-27 10:15:52
 * @VERSION: 1.0
 */
public class AbstractMulticaster implements Multicaster {

    private final Set<Listener> listenerList = new HashSet<>();

    private final Map<Class,Class> eventTypeCache = new HashMap<>();

    @Override
    public void multicasterEvent(Event e, boolean isAsyn) {
        for (Listener listener : listenerList) {
            if(supportEvent(listener,e)){
                if(isAsyn){
                    new Thread(()->listener.onEvent(e)).start();
                }else {
                    listener.onEvent(e);
                }
            }
        }
    }

    @Override
    public void addListener(Listener listener) {
        this.listenerList.add(listener);
    }

    @Override
    public void removeListener(Listener listener) {
        this.listenerList.remove(listener);
    }

    @Override
    public Set<Listener> getListeners() {
        return listenerList;
    }

    public boolean supportEvent(Listener listener,Event e){
        Class<? extends Listener> listenerType = listener.getClass();
        Class<? extends Event> sourceType = e.getClass();
        Class<? extends Event> eventType = eventTypeCache.get(listenerType);
        if(eventType != null && sourceType.isAssignableFrom(eventType)){
            return true;
        }
        Type[] types = listener.getClass().getGenericInterfaces();
        for (Type type : types) {
            if(type instanceof ParameterizedTypeImpl){
                ParameterizedTypeImpl parameterizedType = (ParameterizedTypeImpl) type;
                Type[] typeArguments = parameterizedType.getActualTypeArguments();
                for(Type typeArgument : typeArguments){
                    String typeName = typeArgument.getTypeName();
                    try {
                        eventType = (Class<? extends Event>) Class.forName(typeName);
                        if(sourceType.isAssignableFrom(eventType)){
                            eventTypeCache.putIfAbsent(listenerType,eventType);
                            return true;
                        }
                    } catch (ClassNotFoundException ex) {
                        ex.printStackTrace();
                        return false;
                    }
                }
            }
        }
        return false;
    }
}
